package com.sun.chart;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

import com.sun.chart.data.RadarData;

import java.util.ArrayList;
import java.util.List;

/*
 *
 * #                                                   #
 * #                       _oo0oo_                     #
 * #                      o8888888o                    #
 * #                      88" . "88                    #
 * #                      (| -_- |)                    #
 * #                      0\  =  /0                    #
 * #                    ___/`---'\___                  #
 * #                  .' \\|     |# '.                 #
 * #                 / \\|||  :  |||# \                #
 * #                / _||||| -:- |||||- \              #
 * #               |   | \\\  -  #/ |   |              #
 * #               | \_|  ''\---/''  |_/ |             #
 * #               \  .-\__  '-'  ___/-. /             #
 * #             ___'. .'  /--.--\  `. .'___           #
 * #          ."" '<  `.___\_<|>_/___.' >' "".         #
 * #         | | :  `- \`.;`\ _ /`;.`/ - ` : | |       #
 * #         \  \ `_.   \_ __\ /__ _/   .-` /  /       #
 * #     =====`-.____`.___ \_____/___.-`___.-'=====    #
 * #                       `=---='                     #
 * #     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   #
 * #                                                   #
 * #               佛祖保佑         永无BUG              #
 * #                                                   #
 */

/**
 * Created by ESSC07 on 2022/1/5.
 */
public class RadarChart extends View {
    private int lines = 5;
    private Paint paint;
    private List<List<Point>> points;
    private Point centerPoint;  // 中心点坐标
    private Paint areaPaint;
    private List<Point> areaPoints;
    private List<RadarData> mDatas;

    private float leftMargin, rightMargin, topMargin, bottomMargin;
    private float lineWidth;
    private int lineColor;
    private int areaColor;
    private float fontSize;
    private int fontColor;


    private boolean updateBackground = true;
    private Paint textPaint;

    public RadarChart(Context context) {
        this(context, null);
    }

    public RadarChart(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RadarChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
        initView();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RadarChart);
        lineWidth = array.getDimension(R.styleable.RadarChart_chart_lineWidth, 2);
        lineColor = array.getColor(R.styleable.RadarChart_chart_lineColor, Color.BLACK);
        areaColor = array.getColor(R.styleable.RadarChart_chart_areaColor, Color.WHITE);
        fontSize = array.getDimension(R.styleable.RadarChart_chart_fontSize, dp2px(12));
        fontColor = array.getColor(R.styleable.RadarChart_chart_fontColor, Color.WHITE);
        array.recycle();
    }

    private void initView() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(lineWidth);
        paint.setColor(lineColor);

        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(fontSize);
        textPaint.setColor(Color.WHITE);

        areaPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        areaPaint.setStyle(Paint.Style.FILL);
        areaPaint.setColor(areaColor);
        points = new ArrayList<>();
        areaPoints = new ArrayList<>();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int size = Math.min(width, height);
        setMeasuredDimension(size, size);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (updateBackground) {
            updateBackground = false;
            calcPoints();
        }
        drawText(canvas);
        drawPoly(canvas);
        drawAreas(canvas);
    }

    // 绘制面积
    private void drawAreas(Canvas canvas) {
        if (areaPoints.isEmpty()) return;
        Path path = new Path();
        Point point = areaPoints.get(0);
        path.moveTo(point.x, point.y);
        for (int i = 1; i < areaPoints.size(); i++) {
            point = areaPoints.get(i);
            path.lineTo(point.x, point.y);
        }
        path.close();
        canvas.drawPath(path, areaPaint);
    }


    private void calcTextWidth() {
        Paint.FontMetrics metrics = textPaint.getFontMetrics();
        float startInit = paint.getStrokeWidth() * 0.5f;
        if (mDatas == null || mDatas.isEmpty()) {
            topMargin = rightMargin = leftMargin = bottomMargin = startInit;
            return;
        }
        Rect rect = getCalcSize(mDatas.get(0));
        topMargin = startInit + rect.height() + metrics.bottom;

        if (mDatas.size() > 1) {
            rect = getCalcSize(mDatas.get(1));
            rightMargin = startInit + rect.width() + rect.left * 2;
        } else {
            rightMargin = startInit;
        }
        if (mDatas.size() > 2) {
            rect = getCalcSize(mDatas.get(2));
            bottomMargin = startInit + rect.height() + metrics.bottom;
        } else {
            bottomMargin = startInit;
        }
        if (mDatas.size() > 4) {
            rect = getCalcSize(mDatas.get(4));
            leftMargin = startInit + rect.width() + rect.left * 2;
        } else {
            leftMargin = startInit;
        }
    }

    private Rect getCalcSize(RadarData radarData) {
        Rect rect = new Rect();
        if (!TextUtils.isEmpty(radarData.getTitle()))
            textPaint.getTextBounds(radarData.getTitle(), 0, radarData.getTitle().length(), rect);
        return rect;
    }

    // 绘制描述文本
    private void drawText(Canvas canvas) {
        List<Point> list = this.points.get(0);
        if (mDatas == null) return;
        float x, y;
        for (int i = 0; i < list.size(); i++) {
            if (i >= mDatas.size()) continue;
            Point point = list.get(i);
            RadarData radarData = mDatas.get(i);
            if (radarData.getTitle() == null) continue;
            Rect rect = new Rect();
            textPaint.getTextBounds(radarData.getTitle(), 0, radarData.getTitle().length(), rect);
            Paint.FontMetrics metrics = textPaint.getFontMetrics();
            x = point.x;
            if (point.x < centerPoint.x) {
                textPaint.setTextAlign(Paint.Align.RIGHT);
                x = point.x - rect.left;
                if (point.y < centerPoint.y) {
                    y = point.y;
                } else {
                    textPaint.getTextBounds(radarData.getTitle(), 0, radarData.getTitle().length(), rect);
                    y = point.y + rect.height();
                }
            } else if (point.x == centerPoint.x) {
                textPaint.setTextAlign(Paint.Align.CENTER);
                y = point.y - metrics.descent;
            } else {
                textPaint.setTextAlign(Paint.Align.LEFT);
                x = point.x + rect.left;
                if (point.y < centerPoint.y) {
                    y = point.y;
                } else {
                    textPaint.getTextBounds(radarData.getTitle(), 0, radarData.getTitle().length(), rect);
                    y = point.y + rect.height();
                }
            }
            canvas.drawText(radarData.getTitle(), x, y, textPaint);
        }
    }

    // 绘制底部布局
    private void drawPoly(Canvas canvas) {
        Path path = new Path();
        // 绘制边框
        for (List<Point> list : points) {
            Point point = list.get(0);
            path.moveTo(point.x, point.y);
            for (int i = 1; i < list.size(); i++) {
                point = list.get(i);
                path.lineTo(point.x, point.y);
            }
            path.close();
        }
        //绘制半径
        path.moveTo(centerPoint.x, centerPoint.y);
        for (Point point : points.get(0)) {
            path.lineTo(point.x, point.y);
            path.moveTo(centerPoint.x, centerPoint.y);
        }
        canvas.drawPath(path, paint);
    }

    // 计算点坐标
    private void calcPoints() {
        points.clear();
        final float du = (float) (Math.PI * 2f / 360);
        float centerAngle = 360f / lines;
        float space = Math.max(leftMargin + rightMargin, topMargin + bottomMargin);
        float r = (getWidth() - space) * 0.5f;
        float x = 0;
        float y = 0;
        List<Point> posList = new ArrayList<>();
        for (int i = 0; i < lines; i++) {
            float currentR = r / lines * i;
            // 第一列点
            posList.add(new Point(r + leftMargin, topMargin + currentR));
            // 第二列点
            y = (float) (r - (r - currentR) * Math.cos(du * centerAngle) + topMargin);
            x = (float) (r + (r - currentR) * Math.sin(du * centerAngle) + leftMargin);
            posList.add(new Point(x, y));
            // 第三列点
            x = (float) (r + (r - currentR) * Math.sin(centerAngle * 0.5f * du)) + leftMargin;
            y = (float) (r + (r - currentR) * Math.cos(centerAngle * 0.5f * du)) + topMargin;
            posList.add(new Point(x, y));
            // 第四列点
            x = (float) (r - (r - currentR) * Math.sin(centerAngle * 0.5f * du)) + leftMargin;
            posList.add(new Point(x, y));
            // 第五列点
            y = posList.get(1).y;
            /*x = 2 * r - posList.get(1).x;*/
            x = (float) (r - (r - currentR) * Math.sin(du * centerAngle) + leftMargin);
            posList.add(new Point(x, y));
            points.add(posList);
            posList = new ArrayList<>();
        }
        centerPoint = new Point(leftMargin + r, topMargin + r);
    }

    private void checkBindData(List<RadarData> list) {
        mDatas = list;
    }

    /**
     * 设置数据
     *
     * @param list
     */
    public void setDatas(List<RadarData> list) {
        post(() -> {
            checkBindData(list);
            calcTextWidth();
            calculateAreaPoints(list, 1);
            invalidate();
        });

    }

    private boolean isAniming = false;

    /**
     * 设置数据动画显示
     *
     * @param list
     */
    public void setDatasWithAnim(List<RadarData> list) {
        post(() -> {
            if (isAniming) return;
            isAniming = true;
            checkBindData(list);
            calcTextWidth();
            if (list == null || list.isEmpty()) {
                isAniming = false;
                calculateAreaPoints(list, 1);
                invalidate();
                return;
            }
            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
            animator.setDuration(500);
            animator.setStartDelay(500);
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    isAniming = false;
                }
            });
            animator.addUpdateListener(animation -> {
                float value = (float) animation.getAnimatedValue();
                calculateAreaPoints(list, value);
                invalidate();

            });
            animator.start();
        });
    }

    /**
     * 顺序计算点坐标
     *
     * @param list
     * @param offset 动画系数
     */
    private void calculateAreaPoints(List<RadarData> list, float offset) {
        areaPoints.clear();
        if (list == null || list.isEmpty()) return;
        final float du = (float) (Math.PI * 2f / 360);
        float centerAngle = 360f / lines;
        float x, y;
        RadarData data = list.get(0);
        float space = Math.max(leftMargin + rightMargin, topMargin + bottomMargin);
        float r = (getWidth() - space) * 0.5f;
        float currentR = data.getValue() / (data.getMax() - data.getMin()) * r * offset;
        areaPoints.add(new Point(r + leftMargin, (r - currentR) + topMargin));

        // 第五列点
        if (list.size() > 1) {
            data = list.get(1);
            currentR = data.getValue() / (data.getMax() - data.getMin()) * r * offset;
            x = (float) (r + currentR * Math.sin(centerAngle * du)) + leftMargin;
            y = (float) (r - currentR * Math.cos(du * centerAngle) + topMargin);
            areaPoints.add(new Point(x, y));
        }

        // 第四列点
        if (list.size() > 2) {
            data = list.get(2);
            currentR = data.getValue() / (data.getMax() - data.getMin()) * r * offset;
            x = (float) (r + currentR * Math.sin(centerAngle * 0.5f * du)) + leftMargin;
            y = (float) (r + currentR * Math.cos(centerAngle * 0.5f * du)) + topMargin;
            areaPoints.add(new Point(x, y));
        }

        // 第三列点
        if (list.size() > 3) {
            data = list.get(3);
            currentR = data.getValue() / (data.getMax() - data.getMin()) * r * offset;
            x = (float) (r - currentR * Math.sin(centerAngle * 0.5f * du)) + leftMargin;
            y = (float) (r + currentR * Math.cos(centerAngle * 0.5f * du)) + topMargin;
            areaPoints.add(new Point(x, y));
        }

        // 第二列点
        if (list.size() > 4) {
            data = list.get(4);
            currentR = data.getValue() / (data.getMax() - data.getMin()) * r * offset;

            y = (float) (r - currentR * Math.cos(du * centerAngle) + topMargin);
            x = (float) (r - currentR * Math.sin(du * centerAngle) + leftMargin);
            areaPoints.add(new Point(x, y));
        }

    }

    private int dp2px(float dp) {
        float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }

    class Point {
        public float x;
        public float y;

        public Point(float x, float y) {
            this.x = x;
            this.y = y;
        }
    }
}
